<?php
/*
 * Gepard native template engine, it's slower than pcre based engine, but it's more powerfull and stable
 * 
 * (c) Taras Kudla, 2010
 */
class gViewNative extends gKernel implements gTemplateEngineStructure {

	private $vars = array();
	private $langimage = null;
	private $langlink = null;
	/* ---------------------------------------------------------------------*/
	# maximum operator body size
	private $operatorbodymax = 50;
	# maximum size of if and loop operators size
	private $operatortextmax = 1000;
	private $operatorsupported =
            array("loop","if","count","sys","action","acl","date","typeof","in_array","extcall","exit");
	private $operatorsparsed = 0;
	/* ---------------------------------------------------------------------*/	
	public function bindvar($varname,$varvalue){
		$this->vars[$varname]=$varvalue;
		return true;
	}
	public function deletevar($varname){
		if( array_key_exists($varname,$this->vars))
			unset($this->vars[$varname]);
		return true;
	}
	public function setvars ($vars){
		$this->vars = & $vars;
	}
	
	public function clearallglobal(){
		$this->vars = null;
		$this->vars = array();
		$this->langimage = null;
	}

        public function render($text,$data=null,$i18n=null){
            
        }

	public function & parsetext ( & $text,$data = null, $language = null ){
		$this->clearallglobal();
		if( !is_array($data))$data=array();	
		$this->vars = &$data;
		if( is_object($language) && get_class($language)=="gi18n_image" ){
			$this->langlink=$language;
		}
		$this->parser($text);
		$this->clearallglobal();
		return $text;
	}	
	
	/* ----------------------------------------------------------------------------------------*/
	public function parser( &$text, $from = 0,$maxoperators=100) {
	/*
	 * FIXME: optimization for lang and sys tag
	 */
	$text  =  preg_replace_callback("/\{ *\%lang +([A-z0-9_-]+) *\}/i",array(&$this,"r_langpcre"),$text);
	$text  =  preg_replace_callback("/\{ *\%syslang +([A-z0-9_-]+) *\}/i",array(&$this,"r_syslangpcre"),$text);
	//$text  =  preg_replace_callback("/\{ *\%sys +([A-z0-9_-]) *\}/i",array(&$this,"r_sys"),$text);
	$i=$from;
	$operators = 0;
	$length = strlen($text);
	# seeking for characters	
	while( ($i=strpos($text,"{",$i))!==false ){
		if( !isset($text[$i])) break;
		# checking for new operator symbol
		if( $text[$i] == "{"){
			$startpos = $i;
			# initializing
			$operatorname = "";
			$operatorbody = "";
			$operatortext = "";
                        # finding next position - space and operand symbol
			$operstart = false;
			$is_var = false;
                        # starting from "{" symbol
			for($j=1;$j<=10;$j++){
				if( !isset($text[$i+$j])) break;
				$ichar = $text[$i+$j];
				# space - continue
				if( $ichar==" " )continue;				
				# as function
				else if( $ichar =="%" && isset($text[$i+$j+1]) && $text[$i+$j+1]!=" "){
					$operstart = $i+$j;
					break;
				}
				# as variable
				else if( $ichar =="#" && isset($text[$i+$j+1]) && $text[$i+$j+1]!=" "){
					$is_var= true;
					$operstart = $i+$j;
					break;
				}
				# incorrect symbol - exiting
				 else break;
			}
			if( !$operstart){ $i++; continue; }
			# пошук кінця назви оператора - пробілу ,дужки, }
			$operstart++;
			$operend = false;
			if( !$is_var) {
				for( $j=0;$j<=20;$j++){
					# checking if character exists
					if( !isset($text[$operstart+$j])) break;
					$char = $text[$operstart+$j];
                                        //if( !preg_match("/[A-z]/",$char)){
					if( $char == " " || $char=="("   ){
						$operend=$operstart+$j;
						break;
					}
				}
				if ( !$operend ){ $i++; continue; }
				# parsing operator name
				$operatorname = trim(substr($text,$operstart,($operend-$operstart)));
				//print $operatorname."<hr>";
				# checking if operator name is supported
				if( !in_array($operatorname,$this->operatorsupported)) { $i++; continue; }
			}
			else $operend = $operstart;
			# seeking for end tag symbol - }
			$opencounter = 0;
			$operbodyend = false;
			$j=0;
			while($j<$this->operatorbodymax){
				# checking if symbol exists
				if( !isset($text[$operend+$j])) break;
				$charpos = $operend+$j;
				$char= $text[$charpos];
				$lastchar = $text[$charpos-1];
				if( $char=="{"){
					# TODO: recursive call
					$cpos = $operend+$j;
					$text = $this->parser($text,$cpos,1);
					$j++;
					continue;
				}
				if( $char=="}" && $lastchar!="\\" && $opencounter>0)
					$opencounter--;
				if( $char=="}" && $lastchar!="\\" && $opencounter==0){
					$operbodyend = $operend+$j;
					break;
				}
				$j++;
			}
			if( !$operbodyend){ $i++; continue; }
			$operatorbody = trim(substr($text,$operend,($operbodyend-$operend)));
			$operatorname = strtolower($operatorname);
			# last operator pos for substr_replace
			$endpos=($operbodyend-$startpos+1);
                        # checking if operator has body, means close operator tag looks like: {/operator_name}
			if( ($operatorname=="loop" || $operatorname=="if") && !$is_var){
				$endtag = $operatorname;
				$endtagpos = false;
				$opercounter = 0;
				for($j=0;$j<$this->operatortextmax;$j++){
					$pos = $operbodyend+$j;
					if( !isset($text[$pos])) break;
					$char = $text[$pos];
					if( $char=="{"){
						for($k=1;$k<=10;$k++){
							$kpos = $pos+$k;
							if( !isset($text[$kpos])) break;
							$kchar = $text[$kpos]; 
							//print $kchar;
							if( $kchar == " ") { continue; }
							else if( $kchar=="%" ){
								$len = strlen($operatorname);
								$tl = substr($text,$kpos+1,$len);
								if( $tl==$operatorname){
									$opercounter++;
								}
							}
							else if( $kchar == "/" ){
								$t= substr($text,$kpos+1,strlen($operatorname));
								if( strtolower($t) == $operatorname ){
									$endcorrect = false;
									if( $opercounter>0){
										$opercounter--;
										continue;
									}
									$tmpnum = strlen($operatorname);
									# finding for correct tag close
									for($l=1;$l<=10;$l++){
										$lpos = $kpos+$l+$tmpnum;
										if( !isset($text[$lpos])) break;
										if( $text[$lpos]==" ") continue;
										if( $text[$lpos]=="}"){
											$endcorrect = $lpos;
											break;
										} else break;
										
								}
								if( $endcorrect ) {
									$endtagpos = $pos;
									break;
								}
							} else break;
							
						} else break;
							
					}
					if( $endtagpos )break;
					}
				}
				if( $endtagpos) {
					# tag closed correctly
					$length=($endtagpos-$operbodyend-1);
					$operatortext = substr($text,$operbodyend+1,$length );
					$endpos = $endcorrect - $startpos+1; 
					//$i=$endcorrect;
				} else {
					# loop or if format is not correct, continue
					$i++;
					continue;
				}
				
			}
			$rtext = null;
			# checking if is variable
			if( $is_var) {
				$rtext = $this->r_var($operatorbody);
			}						
			# checking for operator
			
			# checking if 'exit'
			if ( $operatorname=="exit"){
				# exiting if 'exit' detected
				$text = substr($text,0,$i);
				return $text;
			}
			switch( $operatorname) {
				case "sys" : $rtext = $this->r_sys($operatorbody); break;
				case "date" : $rtext = $this->r_date($operatorbody); break;
				case "typeof" : $rtext = $this->r_typeof($operatorbody); break;
				case "extcall" : $rtext = $this->r_extcall($operatorbody); break;
				case "count" : $rtext = $this->r_count($operatorbody); break;
				case "loop": $rtext = $this->r_loop($operatorbody,$operatortext); break;
				case "if" : $rtext = $this->r_if($operatorbody,$operatortext); break;
				case "action" : $rtext = $this->r_action($operatorbody); break;
				case "acl" : $rtext = $this->r_acl($operatorbody); break;
                                case "in_array" : $rtext = $this->r_in_array($operatorbody); break;
				
			}
			# replacing text
			$text = substr_replace($text,$rtext,$startpos,$endpos);
			# inc operators number
			$length = strlen($text);
			$this->operatorsparsed++;
			if( $operators>=$maxoperators)
				break;
			//$i--;
		}
		//$i++;
	}
	return $text;
	}
/**************************************************************************************************/	
	private function r_var($var,$operation="default"){
		if( isset($var[0]) && $var[0]=="#") {
			$var = substr($var,1,strlen($var));
		}
		if (is_array($this->vars)){
			$useeq = false;
			$eqpart = null;
			# checking for new variable
			if( strpos($var,"=")>0){
				# creating new variable
				# FIXME: ugly
				preg_match("/([^=]+) *= *(.+)$/",$var,$tmp);
				$var = trim($tmp[1]);
				$eqpart = $tmp[2];
				if( !is_numeric($eqpart)){
					
				}				
				$useeq = true;
			}
			$p1 = preg_replace("/\[([^0-9]+|[A-z_]+[0-9]{1,3})\]/", "[\"$1\"]", $var);
			$p = preg_replace("/^([^\[]+)/", "[\"$1\"]", $p1);
			$result = null;
			/*
			 * var detection
			 */	
			preg_match_all("/\[\"?([A-z0-9_]+)\"?\]/",$p,$tmp);
			$vars = $tmp[1];
			$deep = count($vars);
			if( $deep>1 && $useeq){
				return "Cannot assign a value to variable '$var'";
			}
			if( $useeq ){
				$this->bindvar($var,$eqpart);
				return null;
			}
			//return null;
			$varname = $vars[0];
			# array calls is to deep, error
			if( $deep > 4 ){
			  	return "Array ( {$var[1]} ) deep is to large";
			}
			if( $deep>0){
				if( array_key_exists($vars[0],$this->vars))
				$var = $this->vars[$vars[0]];
				else return "Unknown part  '{$vars[0]}' of variable #$varname in template engine";
			}
			if( $deep>1){
				if( array_key_exists($vars[1],$var))
				$var = $var[$vars[1]];
				else return "Unknown part  '{$vars[1]}' of variable #".$varname." in template engine";
			}
				if( $deep>2){
				if( array_key_exists($vars[2],$var))
				$var = $var[$vars[2]];
				else return "Unknown part  '{$vars[2]}' of variable #".$varname."[".$vars[1]."] in template engine";
			}	    
			if( $deep>3){
				if( array_key_exists($vars[3],$var))
				$var = $var[$vars[3]];
				else return "Unknown part  '{$vars[3]}' of variable #".$varname."[".$vars[1]."]"."[".$vars[2]."] in template engine";
			}
			switch($operation){
				case "count" : $result = (is_array($var)?count($var):((!is_object($var) && !is_resource($var)) ?strlen((string)$var):0)); break;  
				case "typeof": $result = is_array($var)?'array':(is_object($var)?'object':(is_numeric($var)?'numeric':(is_string($var)?'string':'undefined')));break;
				default: if(is_bool($var))$result=(int)$var; else $result=&$var;
			}
			return $result;	
	       }
	}
	/******************************/
	private function r_sys($data){
		switch($data){
			case "kernel": return $this->core->GetKernel(); break;	
			case "version": return $this->core->GetVersion(); break;		
			case "htmlheaders" : return $this->core->cms->GetHTMLHeaders(); break;
			case "language": return $this->core->get("language"); break;		
			case "debug": return $this->core->GetDebug(_STRING); break;		
			case "runtime": return $this->core->runtimepoint(); break;
			case "time"    : return date("U"); break;
			case "memusage": return $this->core->GetMemoryUsage(); break;
			case "reqfiles": return count(get_required_files()); break;
			case "userid"  : return $this->core->user->GetUserID(); break;
			case "platform" :return PHP_OS; break;
			case "title" :return $this->core->cms->gettitle(); break;
			case "phpversion" : return PHP_VERSION; break;
			case "calltrace": return "calltrace"; break;
			case "rand"	: return rand(0,100); break;
			case "encoding": return $this->core->get("encoding"); break;		
		}
	}
	/*******************************/
	public function r_date($data){
		return date( (($data!="now")?$data:"Y-m-d H:i:s"));	
	}
	/*******************************/
	public function r_in_array($data){
		if( preg_match("/^\(([^\)]+)\)$/",$data,$tmp)){
			$data = $tmp[1];
		}
		$tmp = explode(",",$data);
                $val = $tmp[0];
                $varname = $tmp[1];
    		$type=$this->r_var($varname,"typeof");
		return in_array($val,$this->r_var($varname))?1:0;
	}
	/**********************************/
	public function r_typeof($data){
		return $this->r_var($data,"typeof");
	}
	/***********************************/
	public function r_count($data){
		return $this->r_var($data,"count");
	}
	/************************************/
	public function & r_loop($title,$text){
		$data = "";
		$start = 0;
		$iter = "i";
		$end = 0;
		# checking if iter key defined
		if( preg_match("/^([A-z]{1})\,([^,]+)/",$title,$tmp)){
			$iter = $tmp[1];
			$title = $tmp[2];			
		}
		if( preg_match("/^(\#[A-z0-9_]+)/",$title,$tmp))  {
			$end = $this->r_count($tmp[1]);
		}		
		else if( preg_match("/([A-z]{1}) *= *([0-9]+|\#[A-z0-9_]+)/",$title,$tmp)){
			$iter = $tmp[1];
			if( is_numeric($tmp[2]))
			$end = (int)$tmp[2];
			else 
			$end = $this->r_count($tmp[2]);
		}
		else if( preg_match("/([0-9]+)\.\.([0-9]+)/",$title,$tmp) ||  preg_match("/([0-9]+)\:([0-9]+)/",$title,$tmp)){
			$iter = "i";
			$start =  (int)$tmp[1]-1;
			$end = (int)$tmp[2];
		} else if ( preg_match("/([0-9]+)/",$title,$tmp)){
			$end = (int)$tmp[1];
		} else {
			return $data;
		}
		for($i=$start;$i<$end;$i++){
			$tmp = preg_replace("/\[".$iter."\]/", "[".$i."]", $text);
            		$tmp = preg_replace("/\{".$iter."\}/si", ($i+1), $tmp);
			$data .=$tmp;
		}
		return $data;
	}
	/******************************************/
	public function r_if($title,$text) {

           



		if( preg_match("/([\"'])?( *|[A-z0-9-_]+)[\"']? +(=|>|<|not|%|<=|>=|eq) *([\"'])?([A-z0-9-_]+| *)[\"']?/i",$title,$tmp)){
			$left = $tmp[2];
			$exp = $tmp[3];
			$right = $tmp[5];
			//if( $left[0]=="#") $left = $this->r_var($left);
			//if( $right[0]=="#") $right = $this->r_var($right);			
		} else return null;
		$left=trim($left);
		$left=strlen($left)==0?null:$left;
		$right=trim($right);
		$right=strlen($right)==0?null:$right;
		$true = false;
		$else = null;
		if(preg_match("/(.+)\{else\}(.+)/is",$text,$tmp)){
		        $else = $tmp[2];
			$text = $tmp[1];
			}
			# preparing for defines
			if( $right=="true" || $right=="false"){
				$right=($right=="true")?"1":"0";
			}
			if( $right=="null"){
				$right= null;
			}
			if( $left=="true" || $left=="false"){
				$left=($left=="true")?"1":"0";
			}	
			if( $left=="null"){
				$left= null;
			}	
		switch ($exp){
		    case "eq":
		    case "=" :     
		    	$true=($left == $right)?true:false;
		    	//$true=( $right=="true" || $right=="false")?($left==true?true:false):($left == $right)?true:false;
		        break;
		    case ">":
		        $true=($left > $right) ? true:false;
		        break;            case ">=":
		        $true=($left >= $right) ? true:false;
		        break;
		    case "<=":
		      $true=($left <= $right) ? true:false;
		        break;
		    case "<":
		        $true=($left < $right) ? true:false;
		        break;
		    case "not":
		        $true=($left != $right) ? true:false;
		        break;
		    case "%":
		        $true=((int) $left % (int) $right && ((int) $right != 0)) ? true : false;
		        break;
		    default:
		        $true=false;
		}
		return ($true)?$text:$else;
	}
	/**********************************************************************/
	public function r_lang($key){
		if( is_object($this->langlink) && get_class($this->langlink)=="gi18n_image"){
			return $this->langlink->get($key);
		}	
		if ($this->core->Getenvlayer() == _ENV_KERNEL){
			return $this->core->lang->get($key);
		}
		return $this->core->lang->getmodlangbykey($this->core->getenvcaller("mod"),$key);
	}
	/*************************************************************************/
	public function r_syslang($key){
		return $this->core->lang->get($key);
	}

	/*************************************************************************/
	public function & r_extcall($action){
		$data = $this->core->cms->callaction($action);
		if( !is_string($data)) return null;
		return $data;
	}
	/*************************************************************************/
	public function r_acl($data){
		if( preg_match("/^\(([^\)]+)\)$/",$data,$tmp)){
			$data = $tmp[1];
		} 
		$tmp = explode(",",$data);
		$action = $tmp[0];
		$params = (isset($tmp[1]) && $tmp[1]!="null")?$tmp[1]:null;
		$value =  (isset($tmp[2]) && $tmp[2]!="null")?$tmp[2]:null;
		$optionalhtml =  (isset($tmp[3]) && $tmp[3]!="null")?$tmp[3]:null;
		$runlevelname =  (isset($tmp[4]) && $tmp[4]!="null")?$tmp[4]:null;
		$env = $this->core->Getenvcaller();
		if(preg_match("/^([A-z0-9_]+)-([A-z0-9]+)$/i",$action,$tmp)){
			$module = $tmp[1];
			$function = $tmp[2];	
		} else {
			$module = $env["MOD"];
		$function  = $action;
		}
		return $this->core->cms->GetActionLink( $function,$params,$value,$optionalhtml,$runlevelname,$function);
	}
	/************************************************************/
	public function r_action($data){
		if( preg_match("/^\(([^\)]+)\)$/",$data,$tmp)){
			$data = $tmp[1];
		} 
		$tmp = explode(",",$data);
		$action = ($tmp[0]=="null")?null:$tmp[0];
		$module = null;
		$function = null;
		$params = (isset($tmp[1]) && $tmp[1]!="null")?$tmp[1]:null;
		$rl = isset($tmp[2])?$tmp[2]:null; 
		# 
		$env = $this->core->Getenvcaller();
		if( $action == "this") {
			$module = $env["MOD"];
			$function = $env["NAME"];
		} else 	if( !strpos($action,"-") && !is_null($action)){
			$module = $env["MOD"];
			$function = $action;
		} else if($action==null){
			$function=null;$module=null;
		} else {
			$module=explode("-",$action);
			$function = $module[1];
			$module = $module[0];
		}
		return $this->core->cms->GetActionLink( strtolower($function),$params,$rl,$module );
	}
	/************************************************************/
	public function r_langpcre($data){
		$key = $data[1];
		if( is_object($this->langlink) && get_class($this->langlink)=="gi18n_image"){
			return $this->langlink->get($key);
		}	
		if ($this->core->Getenvlayer() == _ENV_KERNEL){
			return $this->core->i18n->get($key);
		}
		return $this->core->i18n->getmodlangbykey($this->core->getenvcaller("mod"),$key);
	}
	/************************************************************/
	public function r_syslangpcre($data){
		return $this->core->i18n->get($data[1]);
	}
}